<?php

namespace app\common\service;

use think\facade\Cache;
use InvalidArgumentException;
use RuntimeException;

class RedisLockService
{
    /**
     * 锁前缀
     */
    protected string $prefix = 'lock:';

    /**
     * 默认超时时间（秒）
     */
    protected int $expire = 10;

    /**
     * 获取锁的重试间隔（微秒）
     */
    protected int $retryInterval = 100000; // 100ms

    /**
     * 获取锁的最大等待时间（秒）
     */
    protected int $maxWaitTime = 3;

    /**
     * 当前锁的键名
     */
    protected ?string $key = null;

    /**
     * 当前锁的值（用于解锁时验证）
     */
    protected ?string $value = null;

    public function __construct()
    {
        // 可以从配置中读取
        $this->prefix = config('cache.stores.redis.prefix', 'cache:') . 'lock:';
    }

    /**
     * 设置锁前缀
     */
    public function setPrefix(string $prefix): self
    {
        $this->prefix = $prefix;
        return $this;
    }

    /**
     * 设置锁过期时间（防止死锁）
     */
    public function setTimeout(int $seconds): self
    {
        if ($seconds < 1) {
            throw new InvalidArgumentException('Lock timeout must be >= 1 second.');
        }
        $this->expire = $seconds;
        return $this;
    }

    /**
     * 设置最大等待时间（阻塞式获取）
     */
    public function setWaitTime(int $seconds): self
    {
        $this->maxWaitTime = $seconds;
        return $this;
    }

    /**
     * 获取锁（非阻塞）
     *
     * @param string $key 锁的唯一标识（如：withdraw_user_123）
     * @return bool 获取成功返回 true
     */
    public function get(string $key): bool
    {
        $this->key = $this->prefix . $key;
        $this->value = $this->generateValue(); // 唯一值，防止误删

        $result = Cache::store('redis')->set($this->key, $this->value, $this->expire, false);

        if ($result === true || $result === 1) {
            return true;
        }

        return false;
    }

    /**
     * 获取锁（阻塞式，直到超时或成功）
     *
     * @param string $key
     * @return bool
     */
    public function block(string $key): bool
    {
        $this->key = $this->prefix . $key;
        $this->value = $this->generateValue();

        $end = time() + $this->maxWaitTime;

        while (time() < $end) {
            $result = Cache::store('redis')->set($this->key, $this->value, $this->expire, false);
            if ($result) {
                return true;
            }

            // 等待一段时间再试
            usleep($this->retryInterval);
        }

        return false;
    }

    /**
     * 释放锁（必须是自己加的锁才能释放）
     */
    public function release(): bool
    {
        if (!$this->key || !$this->value) {
            return false;
        }

        try {
            // 使用 Lua 脚本保证原子性
            $script = <<<'LUA'
                if redis.call("GET", KEYS[1]) == ARGV[1] then
                    return redis.call("DEL", KEYS[1])
                else
                    return 0
                end
                LUA;

            $result = Cache::store('redis')->getConnection()->evaluate($script, [$this->key], [$this->value]);

            return $result === 1;

        } catch (\Throwable $e) {
            // 记录日志，但不要抛出异常，避免业务中断
            \think\facade\Log::warning('RedisLockService: 释放锁失败', [
                'key' => $this->key,
                'error' => $e->getMessage()
            ]);
            return false;
        } finally {
            // 重置状态
            $this->key = null;
            $this->value = null;
        }
    }

    /**
     * 自动释放锁（在对象销毁时调用）
     */
    public function __destruct()
    {
        if ($this->key && $this->value) {
            $this->release();
        }
    }

    /**
     * 生成唯一值（用于锁的 value）
     */
    protected function generateValue(): string
    {
        return uniqid('lock_', true) . '_' . getmypid() . '_' . mt_rand(1000, 9999);
    }

    /**
     * 判断是否已持有锁
     */
    public function isLocked(): bool
    {
        return $this->key !== null && $this->value !== null;
    }

    /**
     * 获取当前锁键名
     */
    public function getCurrentKey(): ?string
    {
        return $this->key;
    }
}